using System.Runtime.CompilerServices;
using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{


	using Direction = org.neo4j.graphdb.Direction;
	using Node = org.neo4j.graphdb.Node;
	using NotFoundException = org.neo4j.graphdb.NotFoundException;
	using Relationship = org.neo4j.graphdb.Relationship;
	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using ReturnableEvaluator = org.neo4j.graphdb.ReturnableEvaluator;
	using StopEvaluator = org.neo4j.graphdb.StopEvaluator;
	using Traverser = org.neo4j.graphdb.Traverser;
	using Order = org.neo4j.graphdb.Traverser.Order;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using Record = org.neo4j.kernel.impl.nioneo.store.Record;
	using RelationshipChainPosition = org.neo4j.kernel.impl.nioneo.store.RelationshipChainPosition;
	using LockType = org.neo4j.kernel.impl.transaction.LockType;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;
	using IntArray = org.neo4j.kernel.impl.util.IntArray;

	internal class NodeImpl : NeoPrimitive, Node, Comparable<Node>
	{
		private ArrayMap<string, IntArray> relationshipMap = null;
	// private RelationshipGrabber relationshipGrabber = null;
		private RelationshipChainPosition relChainPosition = null;


		internal NodeImpl(int id, NodeManager nodeManager) : base(id, nodeManager)
		{
		}

	// newNode will only be true for NodeManager.createNode
		internal NodeImpl(int id, bool newNode, NodeManager nodeManager) : base(id, newNode, nodeManager)
		{
			if (newNode)
			{
				relationshipMap = new ArrayMap<string, IntArray>();
				relChainPosition = new RelationshipChainPosition(Record.NO_NEXT_RELATIONSHIP.intValue());
			}
		}

		protected internal override void changeProperty(int propertyId, object @value)
		{
			nodeManager.nodeChangeProperty(this, propertyId, @value);
		}

		protected internal override int addProperty(PropertyIndex index, object @value)
		{
			return nodeManager.nodeAddProperty(this, index, @value);
		}

		protected internal override void removeProperty(int propertyId)
		{
			nodeManager.nodeRemoveProperty(this, propertyId);
		}

		protected internal override ArrayMap<int, PropertyData> loadProperties()
		{
			return nodeManager.loadProperties(this);
		}

		internal virtual List<RelTypeElementIterator> getAllRelationships()
		{
			ensureFullRelationships();
			List<RelTypeElementIterator> relTypeList = new LinkedList<RelTypeElementIterator>();
			ArrayMap<string, IntArray> addMap = nodeManager.getCowRelationshipAddMap(this);
			foreach (string type in relationshipMap.Keys)
			{
				IntArray src = relationshipMap.get(type);
				IntArray remove = nodeManager.getCowRelationshipRemoveMap(this, type);
				IntArray add = null;
				if (addMap != null)
				{
					add = addMap.get(type);
				}
				if (src != null || add != null)
				{
					relTypeList.Add(RelTypeElement.create(type, this, src, add, remove));
				}
			}
			if (addMap != null)
			{
				foreach (string type in addMap.Keys)
				{
					if (relationshipMap.get(type) == null)
					{
						IntArray remove = nodeManager.getCowRelationshipRemoveMap(this, type);
						IntArray add = addMap.get(type);
						relTypeList.Add(RelTypeElement.create(type, this, null, add, remove));
					}
				}
			}
			return relTypeList;
		}

		internal virtual List<RelTypeElementIterator> getAllRelationshipsOfType(params RelationshipType[] types)
		{
			ensureFullRelationships();
			List<RelTypeElementIterator> relTypeList = new LinkedList<RelTypeElementIterator>();
			foreach (RelationshipType type in types)
			{
				IntArray src = relationshipMap.get(type.name());
				IntArray remove = nodeManager.getCowRelationshipRemoveMap(this, type.name());
				IntArray add = nodeManager.getCowRelationshipAddMap(this, type.name());
				if (src != null || add != null)
				{
					relTypeList.Add(RelTypeElement.create(type.name(), this, src, add, remove));
				}
			}
			return relTypeList;
		}

		public virtual IEnumerable <Relationship> getRelationships()
		{
			return new IntArrayIterator(getAllRelationships(), this, Direction.BOTH, nodeManager, new RelationshipType[0]);
		}

		public virtual IEnumerable <Relationship> getRelationships(Direction dir)
		{
			return new IntArrayIterator(getAllRelationships(), this, dir, nodeManager, new RelationshipType[0]);
		}

		public virtual IEnumerable <Relationship> getRelationships(RelationshipType type)
		{
			RelationshipType[] types = new RelationshipType[] { type };
			return new IntArrayIterator(getAllRelationshipsOfType(types), this, Direction.BOTH, nodeManager, types);
		}

		public virtual IEnumerable <Relationship> getRelationships(params RelationshipType[] types)
		{
			return new IntArrayIterator(getAllRelationshipsOfType(types), this, Direction.BOTH, nodeManager, types);
		}

		public virtual Relationship getSingleRelationship(RelationshipType type, Direction dir)
		{
			RelationshipType[] types = new RelationshipType[] { type };
			IEnumerator <Relationship> rels = new IntArrayIterator(getAllRelationshipsOfType(types), this, dir, nodeManager, types);
			if (!rels.MoveNext())
			{
				return null;
			}
			Relationship rel = rels.Current;
			if (rels.MoveNext())
			{
				throw new NotFoundException("More than one relationship[" + type + ", " + dir + "] found for " + this);
			}
			return rel;
		}

		public virtual IEnumerable <Relationship> getRelationships(RelationshipType type, Direction dir)
		{
			RelationshipType[] types = new RelationshipType[] { type };
			return new IntArrayIterator(getAllRelationshipsOfType(types), this, dir, nodeManager, types);
		}

		public virtual void delete()
		{
			nodeManager.acquireLock(this, LockType.WRITE);
			bool success = false;
			try
			{
				nodeManager.deleteNode(this);
				success = true;
			}
			finally
			{
				nodeManager.releaseLock(this, LockType.WRITE);
				if (!success)
				{
					setRollbackOnly();
				}
			}
		}

///    
///     <summary> * If object <CODE>node</CODE> is a node, 0 is returned if <CODE>this</CODE>
///     * node id equals <CODE>node's</CODE> node id, 1 if <CODE>this</CODE>
///     * node id is greater and -1 else.
///     * <p>
///     * If <CODE>node</CODE> isn't a node a ClassCastException will be thrown.
///     *  </summary>
///     * <param name="node">
///     *            the node to compare this node with </param>
///     * <returns> 0 if equal id, 1 if this id is greater else -1 </returns>
///     
		public virtual int compareTo(Node n)
		{
			long ourId = this.getId(), theirId = n.getId();

			if (ourId < theirId)
			{
				return -1;
			}
			else if (ourId > theirId)
			{
				return 1;
			}
			else
			{
				return 0;
			}
		}

///    
///     <summary> * Returns true if object <CODE>o</CODE> is a node with the same id as
///     * <CODE>this</CODE>.
///     *  </summary>
///     * <param name="o">
///     *            the object to compare </param>
///     * <returns> true if equal, else false </returns>
///     
		public override bool Equals(object o)
		{
		// verify type and not null, should use Node inteface
			if (!(o is Node))
			{
				return false;
			}

		// The equals contract:
		// o reflexive: x.equals(x)
		// o symmetric: x.equals(y) == y.equals(x)
		// o transitive: ( x.equals(y) && y.equals(z) ) == true
		// then x.equals(z) == true
		// o consistent: the nodeId never changes
			return this.getId() == ((Node) o).getId();

		}

		public override int GetHashCode()
		{
			return id;
		}

///    
///     <summary> * Returns this node's string representation.
///     *  </summary>
///     * <returns> the string representation of this node </returns>
///     
		public override string ToString()
		{
			return "NodeImpl#" + this.getId();
		}

	// caller is responsible for acquiring lock
	// this method is only called when a relationship is created or
	// a relationship delete is undone or when the full node is loaded
		internal virtual void addRelationship(RelationshipType type, int relId)
		{
			IntArray relationshipSet = nodeManager.getCowRelationshipAddMap(this, type.name(), true);
			relationshipSet.Add(relId);
		}

	// caller is responsible for acquiring lock
	// this method is only called when a undo create relationship or
	// a relationship delete is invoked.
		internal virtual void removeRelationship(RelationshipType type, int relId)
		{
			IntArray relationshipSet = nodeManager.getCowRelationshipRemoveMap(this, type.name(), true);
			relationshipSet.Add(relId);
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		private bool ensureFullRelationships()
		{
			if (relationshipMap == null)
			{
				this.relChainPosition = nodeManager.getRelationshipChainPosition(this);
				this.relationshipMap = new ArrayMap<string, IntArray>();
				getMoreRelationships();
				return true;
			}
			return false;
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual bool getMoreRelationships()
		{
			if (!relChainPosition.hasMore())
			{
				return false;
			}
			ArrayMap<string, IntArray> addMap = nodeManager.getMoreRelationships(this);
			if (addMap.size() == 0)
			{
				return false;
			}
			foreach (string type in addMap.Keys)
			{
				IntArray addRels = addMap.get(type);
				IntArray srcRels = relationshipMap.get(type);
				if (srcRels == null)
				{
					relationshipMap.put(type, addRels);
				}
				else
				{
					srcRels.AddAll(addRels);
				}
			}
			return true;
		}

		public virtual Relationship createRelationshipTo(Node otherNode, RelationshipType type)
		{
			return nodeManager.createRelationship(this, otherNode, type);
		}

		public virtual Traverser traverse(Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType relationshipType, Direction direction)
		{
			if (direction == null)
			{
				throw new IllegalArgumentException("Null direction");
			}
			if (relationshipType == null)
			{
				throw new IllegalArgumentException("Null relationship type");
			}
		// rest of parameters will be validated in traverser package
			return nodeManager.createTraverser(traversalOrder, this, relationshipType, direction, stopEvaluator, returnableEvaluator);
		}

		public virtual Traverser traverse(Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType firstRelationshipType, Direction firstDirection, RelationshipType secondRelationshipType, Direction secondDirection)
		{
			if (firstDirection == null || secondDirection == null)
			{
				throw new IllegalArgumentException("Null direction, " + "firstDirection=" + firstDirection + "secondDirection=" + secondDirection);
			}
			if (firstRelationshipType == null || secondRelationshipType == null)
			{
				throw new IllegalArgumentException("Null rel type, " + "first=" + firstRelationshipType + "second=" + secondRelationshipType);
			}
		// rest of parameters will be validated in traverser package
			RelationshipType[] types = new RelationshipType[2];
			Direction[] dirs = new Direction[2];
			types[0] = firstRelationshipType;
			types[1] = secondRelationshipType;
			dirs[0] = firstDirection;
			dirs[1] = secondDirection;
			return nodeManager.createTraverser(traversalOrder, this, types, dirs, stopEvaluator, returnableEvaluator);
		}

		public virtual Traverser traverse(Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, params object[] relationshipTypesAndDirections)
		{
			int length = relationshipTypesAndDirections.length;
			if ((length % 2) != 0 || length == 0)
			{
				throw new IllegalArgumentException("Variable argument should " + " consist of [RelationshipType,Direction] pairs");
			}
			int elements = relationshipTypesAndDirections.length / 2;
			RelationshipType[] types = new RelationshipType[elements];
			Direction[] dirs = new Direction[elements];
			int j = 0;
			for (int i = 0; i < elements; i++)
			{
				object relType = relationshipTypesAndDirections[j++];
				if (!(relType is RelationshipType))
				{
					throw new IllegalArgumentException("Expected RelationshipType at var args pos " + (j - 1) + ", found " + relType);
				}
				types[i] = (RelationshipType) relType;
				object direction = relationshipTypesAndDirections[j++];
				if (!(direction is Direction))
				{
					throw new IllegalArgumentException("Expected Direction at var args pos " + (j - 1) + ", found " + direction);
				}
				dirs[i] = (Direction) direction;
			}
			return nodeManager.createTraverser(traversalOrder, this, types, dirs, stopEvaluator, returnableEvaluator);
		}

		public virtual bool hasRelationship()
		{
			return getRelationships().GetEnumerator().MoveNext();
		}

		public virtual bool hasRelationship(params RelationshipType[] types)
		{
			return getRelationships(types).GetEnumerator().MoveNext();
		}

		public virtual bool hasRelationship(Direction dir)
		{
			return getRelationships(dir).GetEnumerator().MoveNext();
		}

		public virtual bool hasRelationship(RelationshipType type, Direction dir)
		{
			return getRelationships(type, dir).GetEnumerator().MoveNext();
		}

		protected internal virtual void commitRelationshipMaps(ArrayMap<string, IntArray> cowRelationshipAddMap, ArrayMap<string, IntArray> cowRelationshipRemoveMap)
		{
			if (relationshipMap == null)
			{
			// we will load full in some other tx
				return;
			}
			if (cowRelationshipAddMap != null)
			{
				foreach (string type in cowRelationshipAddMap.Keys)
				{
					IntArray add = cowRelationshipAddMap.get(type);
					IntArray remove = null;
					if (cowRelationshipRemoveMap != null)
					{
						remove = cowRelationshipRemoveMap.get(type);
					}
					IntArray src = relationshipMap.get(type);
					relationshipMap.put(type, IntArray.ComposeNew(src, add, remove));
				}
			}
			if (cowRelationshipRemoveMap != null)
			{
				foreach (string type in cowRelationshipRemoveMap.Keys)
				{
					if (cowRelationshipAddMap != null && cowRelationshipAddMap.get(type) != null)
					{
						continue;
					}
					IntArray src = relationshipMap.get(type);
					IntArray remove = cowRelationshipRemoveMap.get(type);
					relationshipMap.put(type, IntArray.ComposeNew(src, null, remove));
				}
			}
		}

		internal virtual RelationshipChainPosition getRelChainPosition()
		{
			return relChainPosition;
		}
	}
}